package etcdclient

import (
	"context"
	"fmt"
	"time"

	"go.etcd.io/etcd/client/v3/concurrency"

	"gitee.com/saroeleven/ginrcpv2/log"
	etcdv3 "go.etcd.io/etcd/client/v3"
)

type etcdClient struct {
	cli    *etcdv3.Client
	config etcdv3.Config
}

// NewEtcdClient creates a new etcdClient instance with the config options
func NewEtcdClient() (*etcdClient, error) {
	client := &etcdClient{
		config: etcdv3.Config{
			Endpoints:         parseFlags().Endpoints,
			Username:          parseFlags().Username,
			Password:          parseFlags().Password,
			DialTimeout:       5 * time.Second,
			DialKeepAliveTime: 30 * time.Second,
		},
	}
	// Create the etcd client
	cli, err := etcdv3.New(client.config)
	if err != nil {
		return nil, fmt.Errorf("failed to create etcd client: %w", err)
	}
	client.cli = cli

	return client, nil
}

// Register registers a service with a given key and value with a TTL
func (client *etcdClient) Register(ctx context.Context, key, val string, ttl int64) error {
	lease, err := client.cli.Grant(ctx, ttl)
	if err != nil {
		return fmt.Errorf("failed to create lease: %w", err)
	}

	if _, err = client.cli.Put(ctx, key, val, etcdv3.WithLease(lease.ID)); err != nil {
		return fmt.Errorf("failed to register key-value pair: %w", err)
	}

	// keepalive
	keepRespChan, err := client.cli.KeepAlive(ctx, lease.ID)
	if err != nil {
		return fmt.Errorf("failed to keep alive lease: %w", err)
	}
	log.Infof("%s: start keepalive for lease %d", key, lease.ID)

	go func() {
		defer func() {
			if r := recover(); r != nil {
				log.Errorf("panic in keepalive goroutine: %v", r)
			}
		}()

		for {
			select {
			case <-ctx.Done():
				log.Infof("%s: context cancelled, stopping keepalive", key)
				return
			case keepResp, ok := <-keepRespChan:
				if !ok {
					log.Errorf("%s: keepalive channel closed", key)
					return
				}
				_ = keepResp // Can be used for monitoring if needed
			}
		}
	}()

	return nil
}

// Lock acquires a distributed lock with the given name and TTL
func (client *etcdClient) Lock(ctx context.Context, lockName string, ttl int, broadcasts ...func()) error {
	ctx, cancel := context.WithCancel(ctx)
	defer func() {
		cancel()
		_ = client.cli.Close()
	}()

	session, err := concurrency.NewSession(client.cli, concurrency.WithTTL(ttl))
	if err != nil {
		return fmt.Errorf("failed to create etcd session: %w", err)
	}
	defer session.Close()

	mutex := concurrency.NewMutex(session, lockName)
	if err = mutex.Lock(ctx); err != nil {
		return fmt.Errorf("failed to acquire lock: %w", err)
	}
	defer func() {
		if err := mutex.Unlock(ctx); err != nil {
			log.Errorf("failed to release lock: %v", err)
		}
	}()

	for _, broadcast := range broadcasts {
		broadcast()
	}

	return nil
}
